/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
/**
 * Software License, Version 1.0
 *
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 *
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 *
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University.  For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering,
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 *
 *
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of
 * any other entity.  Indiana University disclaims any liability to any
 * recipient for claims brought by any other entity based on infringement of
 * intellectual property rights or otherwise.
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.service.time.ntptime;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

/**
 *                         1                   2                   3
 *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                           Seconds                             |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                  Seconds Fraction (0-padded)                  |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 *  This class encapsulates the notion of a timestamp as in rfc2030.
 *  Logically it is the number of seconds since the beginning of the
 *  centure (in UTC time).
 *  It is represented as an 8 byte array, the first four bytes
 *  representing seconds and the next 4 bytes representing second
 *  fractions.
 * @author Hasan Bulut - hbulut@indiana.edu
*/
public class NtpTimestamp {
  //~ Static fields/initializers -----------------------------------------------

  private static TimeZone UTC      = new SimpleTimeZone(0, "UTC");
  private static Calendar calendar = new GregorianCalendar(1900,
                                                           Calendar.JANUARY, 1,
                                                           0, 0, 0);
  private static Date startOfCentury;

  static {
    calendar.setTimeZone(UTC);
    startOfCentury = calendar.getTime();
  }

  //~ Instance fields ----------------------------------------------------------

  private Date   date;
  private byte[] timestampData      = { 0, 0, 0, 0, 0, 0, 0, 0 };
  private long   fractionalPart     = 0;
  private long   integerPart        = 0;
  private long   timeInMilliSeconds = 0;

  //~ Constructors -------------------------------------------------------------

  /**
   * This constructs a timestamp initialized with the current updated date.
   * Date is updated using the offset value between the local machine and
   * atomic time servers.
   */
  public NtpTimestamp() {
    date = new Date();
    timeInMilliSeconds = date.getTime();
    long msSinceStartOfCentury = timeInMilliSeconds - startOfCentury.getTime();
    integerPart    = msSinceStartOfCentury / 1000;
    fractionalPart = ((msSinceStartOfCentury % 1000) * 0x100000000L) / 1000;
    long temp1     = integerPart;
    for (int i = 3; i >= 0; i--) {
      timestampData[i] = (byte) (temp1 % 256);
      temp1            = temp1 / 256;
    }
    temp1 = fractionalPart;
    for (int i = 7; i >= 4; i--) {
      timestampData[i] = (byte) (temp1 % 256);
      temp1            = temp1 / 256;
    }
  }

  /**
   * This constructs a timestamp initialized with the data passed.
   *
   * @param offset int - data is read starting from offset value
   * @param data byte [] - byte array that contains timestamp information
   */
  public NtpTimestamp(int offset, byte[] data) {
    this.setTimestamp(offset, data);
  }

  //~ Methods ------------------------------------------------------------------

  /**
   * Gets the date associated to this NtpTimestamp.
   *
   * @return Date - Date object of this NtpTimestamp
   */
  public Date getTime() {
    return date;
  }

  /**
   * Gets the millisecond value of this NtpTimestamp
   *
   * @return long - time value in milliseconds
   */
  public long getTimeInMilliSeconds() {
    return timeInMilliSeconds;
  }

  /**
   * Sets the NtpTimestamp with the given Date object
   *
   * @param date Date - NtpTimestamp is initialized with this Date object
   */
  public void setTimestamp(Date date) {
    this.date          = date;
    timeInMilliSeconds = date.getTime();
    long msSinceStartOfCentury = timeInMilliSeconds - startOfCentury.getTime();
    integerPart    = msSinceStartOfCentury / 1000;
    fractionalPart = ((msSinceStartOfCentury % 1000) * 0x100000000L) / 1000;
    long temp      = integerPart;
    for (int i = 3; i >= 0; i--) {
      timestampData[i] = (byte) (temp % 256);
      temp             = temp / 256;
    }
    temp = fractionalPart;
    for (int i = 7; i >= 4; i--) {
      timestampData[i] = (byte) (temp % 256);
      temp             = temp / 256;
    }
  }

  /**
   * Sets the timestamp with the given byte array and the start offset
   * within the array. Byte array should have size of offset+8 at least.
   *
   * @param offset int - start offset within the given byte array
   * @param data byte [] - contains NtpTimestamp information.
   */
  public void setTimestamp(int offset, byte[] data) {
    integerPart = 0;
    for (int i = 0; i <= 3; i++) {
      integerPart      = 256 * integerPart + mp(data[offset + i]);
      timestampData[i] = data[offset + i];
    }
    fractionalPart = 0;
    for (int i = 4; i <= 7; i++) {
      fractionalPart   = 256 * fractionalPart + mp(data[offset + i]);
      timestampData[i] = data[offset + i];
    }
    date = new Date(this.setTimeInMilliSeconds());
  }

  /**
   * Initializes the data of the NtpTimestamp with the given byte array
   *
   * @param timestampData byte [] - data of NtpTimestamp is initialized with
   * this byte array
   */
  public void setTimestampData(byte[] timestampData) {
    this.timestampData = timestampData;
  }

  /**
   * Returns timestamp data which keeps the time information in byte array
   * format.
   *
   * @return byte [] - data of NtpTimestamp
   */
  public byte[] getTimestampData() {
    return timestampData;
  }

  /**
   * Converts the date object to String format.
   *
   * @return String - String format representing date with fractional part
   */
  public String toString() {
    return "" + date + " + " + fractionalPart + "/" + 0x100000000L;
  }

  /**
   * Computes the time of the date in milliseconds using integer and fraction
   * part of the timestamp.
   *
   * @return long - NtpTimestamp time in milliseconds
   */
  private long setTimeInMilliSeconds() {
    this.timeInMilliSeconds = (integerPart * 1000 +
                              (fractionalPart * 1000) / 0x100000000L) +
                              startOfCentury.getTime();
    return this.timeInMilliSeconds;
  }

  /**
   * Sets the date of the timestamp to the given milliseconds.
   *
   * @param timeInMilliSeconds - NtpTimestamp is initialized with this time
   * value
   *
   * @return long - NtpTimestamp time in milliseconds
   */
  private long setTimeInMilliSeconds(long timeInMilliSeconds) {
    date = new Date(timeInMilliSeconds);
    this.setTimestamp(date);
    this.timeInMilliSeconds = timeInMilliSeconds;
    return this.timeInMilliSeconds;
  }

  /**
   * If int value of byte is negative, it is returned after added with 256,
   * otherwise the int value of the byte is returned.
   *
   * @param b byte
   *
   * @return  int
   */
  private int mp(byte b) {
    int bb = b;
    return (bb < 0) ? 256 + bb : bb;
  }
}
